﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using ICSharpCode.SharpZipLib.Checksums;
using ICSharpCode.SharpZipLib.Zip;

namespace Kay.OfficeHandler
{
    /// <summary> 
    /// Zip 压缩解压帮助器
    /// </summary> 
    public class ZipHelper
    {
        #region 压缩模块
        /// <summary> 
        /// 递归压缩文件夹内的所有文件
        /// </summary> 
        /// <param name="folderFullName">要压缩的文件夹路径</param> 
        /// <param name="stream">压缩输出流</param> 
        /// <param name="parentDirectory">文件夹上级目录</param> 
        /// <returns>True 压缩成功，否则为 False。</returns> 
        private static bool CompressDirectory(string folderFullName, ZipOutputStream stream, string parentDirectory = "")
        {
            var result = true;
            FileStream fileStream = null;
            var crc = new Crc32();

            try
            {
                var zipEntry = new ZipEntry(Path.Combine(parentDirectory, Path.GetFileName(folderFullName) + "/"));
                stream.PutNextEntry(zipEntry);
                stream.Flush();

                var files = Directory.GetFiles(folderFullName);
                foreach (var file in files)
                {
                    fileStream = File.OpenRead(file);

                    var bytes = new byte[fileStream.Length];
                    fileStream.Read(bytes, 0, bytes.Length);
                    zipEntry = new ZipEntry(Path.Combine(parentDirectory, Path.GetFileName(folderFullName) + "/" + Path.GetFileName(file)))
                    {
                        DateTime = DateTime.Now,
                        Size = fileStream.Length
                    };

                    fileStream.Close();

                    crc.Reset();
                    crc.Update(bytes);

                    zipEntry.Crc = crc.Value;
                    stream.PutNextEntry(zipEntry);
                    stream.Write(bytes, 0, bytes.Length);
                }
            }
            catch
            {
                result = false;
            }
            finally
            {
                if (fileStream != null)
                {
                    fileStream.Close();
                    fileStream.Dispose();
                }
                GC.Collect();
                GC.Collect(1);
            }

            var folders = Directory.GetDirectories(folderFullName);
            return folders.All(folder => CompressDirectory(folder, stream, folderFullName)) && result;
        }

        /// <summary> 
        /// 压缩文件夹
        /// </summary> 
        /// <param name="folderFullName">要压缩的文件夹路径</param> 
        /// <param name="zipFullName">压缩文件完整名称</param> 
        /// <param name="pwd">加密压缩密码</param> 
        /// <returns>True 压缩成功，否则为 False。</returns> 
        public static bool CompressDirectory(string folderFullName, string zipFullName, string pwd = null)
        {
            if (!Directory.Exists(folderFullName))
                return false;

            var zipStream = new ZipOutputStream(File.Create(zipFullName));
            zipStream.SetLevel(6);
            if (!string.IsNullOrEmpty(pwd))
                zipStream.Password = pwd;

            var result = CompressDirectory(folderFullName, zipStream);

            zipStream.Finish();
            zipStream.Close();

            return result;
        }

        /// <summary> 
        /// 压缩文件
        /// </summary> 
        /// <param name="fullName">要压缩的文件名称</param> 
        /// <param name="zipFullName">压缩文件完整名称</param> 
        /// <param name="pwd">加密压缩密码</param> 
        /// <returns>True 压缩成功，否则为 False。</returns> 
        public static bool CompressFile(string fullName, string zipFullName, string pwd = null)
        {
            var result = true;
            ZipOutputStream zipOutputStream = null;
            FileStream fileStream = null;

            if (!File.Exists(fullName))
                return false;

            try
            {
                fileStream = File.OpenRead(fullName);
                var bytes = new byte[fileStream.Length];
                fileStream.Read(bytes, 0, bytes.Length);
                fileStream.Close();

                fileStream = File.Create(zipFullName);
                zipOutputStream = new ZipOutputStream(fileStream);
                if (!string.IsNullOrEmpty(pwd))
                    zipOutputStream.Password = pwd;
                var zipEntry = new ZipEntry(Path.GetFileName(fullName));
                zipOutputStream.PutNextEntry(zipEntry);
                zipOutputStream.SetLevel(6);

                zipOutputStream.Write(bytes, 0, bytes.Length);
            }
            catch
            {
                result = false;
            }
            finally
            {
                if (zipOutputStream != null)
                {
                    zipOutputStream.Finish();
                    zipOutputStream.Close();
                }
                if (fileStream != null)
                {
                    fileStream.Close();
                    fileStream.Dispose();
                }
            }
            GC.Collect();
            GC.Collect(1);

            return result;
        }

        /// <summary>
        /// 压缩多个文件文件
        /// </summary>
        /// <param name="zipFullName">压缩文件完整名称</param>
        /// <param name="pwd">加密压缩密码</param>
        /// <param name="fullNames">要压缩的文件名称</param>
        /// <returns>True 压缩成功，否则为 False。</returns>
        public static bool CompressFiles(string zipFullName, string pwd = null, params string[] fullNames)
        {
            if (fullNames.Length == 0)
            {
                return false;
            }
            try
            {
                var filenames = fullNames;
                using (var zipOutputStream = new ZipOutputStream(File.Create(zipFullName)))
                {
                    zipOutputStream.SetLevel(6); // 压缩级别 0-9
                    if (!string.IsNullOrEmpty(pwd))
                        zipOutputStream.Password = pwd;
                    byte[] buffer = new byte[4096]; //缓冲区大小
                    foreach (var file in filenames)
                    {
                        var zipEntry = new ZipEntry(Path.GetFileName(file))
                        {
                            DateTime = DateTime.Now
                        };
                        zipOutputStream.PutNextEntry(zipEntry);
                        using (var fs = File.OpenRead(file))
                        {
                            int sourceBytes;
                            do
                            {
                                sourceBytes = fs.Read(buffer, 0, buffer.Length);
                                zipOutputStream.Write(buffer, 0, sourceBytes);
                            } while (sourceBytes > 0);
                        }
                    }
                    zipOutputStream.Finish();
                    zipOutputStream.Close();
                }
            }
            catch (Exception)
            {
                return false;
            }

            return true;
        }

        /// <summary> 
        /// 压缩文件或文件夹 
        /// </summary> 
        /// <param name="fullName">要压缩的文件或文件夹名称</param> 
        /// <param name="zipFullName">压缩文件完整名称</param> 
        /// <param name="pwd">加密压缩密码</param> 
        /// <returns>True 压缩成功，否则为 False。</returns> 
        public static bool Compress(string fullName, string zipFullName, string pwd = null)
        {
            var result = false;
            if (Directory.Exists(fullName))
                result = CompressDirectory(fullName, zipFullName, pwd);
            else if (File.Exists(fullName))
                result = CompressFile(fullName, zipFullName, pwd);

            return result;
        }
        #endregion

        #region 解压

        /// <summary> 
        /// 解压功能(解压压缩文件到指定目录) 
        /// </summary> 
        /// <param name="zipFullName">待解压的文件</param> 
        /// <param name="filesFolder">指定解压目标目录</param> 
        /// <param name="pwd">密码</param> 
        /// <returns>解压结果</returns> 
        public static bool Uncompress(string zipFullName, string filesFolder, string pwd = null)
        {
            if (!File.Exists(zipFullName))
                return false;
            if (!Directory.Exists(filesFolder))
                Directory.CreateDirectory(filesFolder);
            try
            {
                using (var zipInputStream = new ZipInputStream(File.OpenRead(zipFullName)))
                {
                    if (!string.IsNullOrEmpty(pwd))
                        zipInputStream.Password = pwd;
                    ZipEntry zipEntry;
                    while ((zipEntry = zipInputStream.GetNextEntry()) != null)
                    {
                        var dirName = Path.GetDirectoryName(zipEntry.Name);
                        Debug.Assert(dirName != null, "dirName != null");
                        var directoryName = Path.Combine(filesFolder, dirName);
                        var fiName = Path.GetFileName(zipEntry.Name);
                        Debug.Assert(fiName != null, "fiName != null");
                        var fileName = Path.Combine(filesFolder, fiName);
                        if (!string.IsNullOrEmpty(directoryName) && directoryName.Trim() != string.Empty)
                        {
                            Directory.CreateDirectory(directoryName);
                        }
                        var isFoder = Path.GetFileName(zipEntry.Name);
                        if (string.IsNullOrEmpty(isFoder) || isFoder.Trim() == string.Empty)
                            continue;

                        if (string.IsNullOrEmpty(fileName) || fileName.Trim() == string.Empty)
                            continue;

                        using (var fileStream = File.Create(fileName))
                        {
                            var bytes = new byte[2048];
                            while (true)
                            {
                                var size = zipInputStream.Read(bytes, 0, bytes.Length);
                                if (size > 0)
                                {
                                    fileStream.Write(bytes, 0, size);
                                }
                                else
                                {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
                return false;
            }
            finally
            {
                GC.Collect();
                GC.Collect(1);
            }
            return true;
        }

        /// <summary> 
        /// 解压功能(解压压缩文件到指定目录) 
        /// </summary>
        /// <param name="zipFullName">待解压的文件</param>
        /// <param name="filesFolder">指定解压目标目录</param>
        /// <param name="files"></param>
        /// <param name="pwd">密码</param>
        /// <returns>解压结果</returns> 
        public static bool UncompressWithRename(string zipFullName, string filesFolder, out List<string> files, string pwd = null)
        {
            var timeStr = DateTime.Now.ToString("yyyyMMddHHmmssfff");
            files = new List<string>();
            if (!File.Exists(zipFullName))
                return false;
            if (!Directory.Exists(filesFolder))
                Directory.CreateDirectory(filesFolder);
            try
            {
                using (var zipInputStream = new ZipInputStream(File.OpenRead(zipFullName)))
                {
                    if (!string.IsNullOrEmpty(pwd))
                        zipInputStream.Password = pwd;
                    ZipEntry zipEntry;
                    while ((zipEntry = zipInputStream.GetNextEntry()) != null)
                    {
                        var dirName = Path.GetDirectoryName(zipEntry.Name);
                        Debug.Assert(dirName != null, "dirName != null");
                        var directoryName = Path.Combine(filesFolder, dirName);
                        var fiName = Path.GetFileName(zipEntry.Name);
                        Debug.Assert(fiName != null, "fiName != null");
                        var fileName = Path.Combine(filesFolder, fiName);
                        if (!string.IsNullOrEmpty(directoryName) && directoryName.Trim() != string.Empty)
                        {
                            if (!Directory.Exists(directoryName))
                            {
                                Directory.CreateDirectory(directoryName);
                            }
                        }
                        var isFoder = Path.GetFileName(zipEntry.Name);
                        if (string.IsNullOrEmpty(isFoder) || isFoder.Trim() == string.Empty)
                            continue;

                        if (string.IsNullOrEmpty(fileName) || fileName.Trim() == string.Empty)
                            continue;

                        if (File.Exists(fileName))
                        {
                            var fileInfo = new FileInfo(fileName);
                            var dir = fileInfo.DirectoryName;
                            Debug.Assert(dir != null, "dir != null");
                            var dirInfo = new DirectoryInfo(dir);
                            var name = fileInfo.Name;
                            var ext = fileInfo.Extension;
                            var fileInfos = dirInfo.GetFiles(name + ".old.*" + ext);
                            if (fileInfos.Length != 0)
                            {
                                foreach (var info in fileInfos)
                                {
                                    try
                                    {
                                        info.Delete();
                                    }
                                    catch (Exception)
                                    {
                                        // ignored
                                    }
                                }
                            }
                            var newName = name + ".old." + timeStr + ext;
                            var newFullName = Path.Combine(dir, newName);
                            fileInfo.MoveTo(newFullName);
                            files.Add(newFullName);
                        }

                        using (var fileStream = File.Create(fileName))
                        {
                            var bytes = new byte[2048];
                            while (true)
                            {
                                var size = zipInputStream.Read(bytes, 0, bytes.Length);
                                if (size > 0)
                                {
                                    fileStream.Write(bytes, 0, size);
                                }
                                else
                                {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
                return false;
            }
            finally
            {
                GC.Collect();
                GC.Collect(1);
            }
            return true;
        }
        #endregion
    }
}